package com.google.code.jbauble.mvc.support;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import com.google.code.jbauble.mvc.DefaultConfiguration;
import com.google.code.jbauble.mvc.DispatcherServlet;

public class ActionParameter {
	private Object controller = null;
	private Method method = null;
	private String[] paramNames = null;
	private Class[] paramTypes = null;
	private static ClassPool pool = null;
	private static ConvertUtilsBeanEx converter = new ConvertUtilsBeanEx();
	private int maxUploadFile;
	private String tempPath = null;

	public ActionParameter(Object controller, Method method, int maxUploadFile) throws NotFoundException {
		this.controller = controller;
		this.method = method;
		paramTypes = method.getParameterTypes();
	
		if (pool == null) {
			initPool();
		}
		
		paramNames = getMethodParameterNames();
		this.maxUploadFile = maxUploadFile;
		tempPath = System.getProperty("java.io.tmpdir");
	}

	private synchronized void initPool() throws NotFoundException {
		if (pool != null)
			return;
		String path = WebLoader.getWebClassesPath(System.getProperty(DefaultConfiguration.WEB_CONF_PATH));
		pool = ClassPool.getDefault();
		pool.insertClassPath(path);
		pool.insertClassPath(new ClassClassPath(this.getClass()));
	}

	private String[] getMethodParameterNames() throws NotFoundException {
		if (paramTypes == null || paramTypes.length < 1)
			return null;
		
		CtMethod cm = null;
		CtClass cc = pool.get(controller.getClass().getName());
		CtClass[] pc = getMethodParameterClasses();
		cm = cc.getDeclaredMethod(method.getName(), pc);
		MethodInfo methodInfo = cm.getMethodInfo();  
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();  
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute  
                .getAttribute(LocalVariableAttribute.tag);         
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        
		int len = paramTypes.length;
		String[] pn = new String[len];
		for (int i=0; i<len; i++) {
			pn[i] = attr.variableName(i + pos);
		}
		return pn;
	}

	private CtClass[] getMethodParameterClasses() throws NotFoundException {
		Class[] cs = method.getParameterTypes();
		if (cs == null || cs.length < 1)
			return null;
		
		CtClass[] cts = new CtClass[cs.length];
		for (int i=0; i<cs.length; i++) {
			cts[i] = pool.get(cs[i].getName());
		}
		return cts;
	}

	public Object[] getParameters(String[] appends, HttpServletRequest request,
			HttpServletResponse response) {
		if (paramTypes == null || paramTypes.length < 1)
			return null;
		
		int appendLen = 0;
		if (appends != null && appends.length > 0) {
			appendLen = appends.length;
		}
		int len = paramTypes.length;
		Object[] args = new Object[paramTypes.length];

		for (int i=0; i<appendLen; i++) {
			Class mc = paramTypes[i];
			if (!isSimpleValueType(mc))
				continue;
			
			args[i] = converter.convert(appends[i], mc);
		}
		for (int i = appendLen; i < len; i++) {
			Class mc = paramTypes[i];

			if (isSimpleValueType(mc)) {
				args[i] = simpleRequestParam(paramNames[i], mc, request);
			}
			else if (HttpServletRequest.class.isAssignableFrom(mc)){
				args[i] = request;
			}
			else if (HttpServletResponse.class.isAssignableFrom(mc)){
				args[i] = response;
			}
			else if (FileItem.class.isAssignableFrom(mc)) {
				args[i] = getFileFromRequest(request);
			}
			else if (mc.isArray() && FileItem.class.isAssignableFrom(mc.getComponentType())) {
				args[i] = getFilesFromRequest(request);
			}
			else {
				args[i] = instanceObject(mc, request);
			}
		}

		return args;
	}
	
	private FileItem getFileFromRequest(HttpServletRequest request) {
		FileItem[] fs = getFilesFromRequest(request);
		if (fs == null || fs.length < 1)
			return null;
		return fs[0];
	}

	private FileItem[] getFilesFromRequest(HttpServletRequest request) {
		if (!ServletFileUpload.isMultipartContent(request))
			return null;
		
		DiskFileItemFactory dfif = new DiskFileItemFactory();  
        dfif.setSizeThreshold(4096);// 设置上传文件时用于临时存放文件的内存大小,这里是4K.多于的部分将临时存在硬盘  
        dfif.setRepository(new File(tempPath));
        // 设置存放临时文件的目录,web根目录下的ImagesUploadTemp目录  
  
        // 用以上工厂实例化上传组件  
        ServletFileUpload sfu = new ServletFileUpload(dfif);
        sfu.setFileSizeMax(maxUploadFile);
        // 从request得到 所有 上传域的列表  
        List<FileItem> fileList = null;  
        try {  
            fileList = sfu.parseRequest(request);  
        } catch (FileUploadException e) {// 处理文件尺寸过大异常  
            e.printStackTrace();
        }  
        // 没有文件上传  
        if (fileList == null || fileList.size() == 0) {
            return null;
        }
        
        int len = fileList.size();
        FileItem[] fis = new FileItem[len];
        for (int i=0; i<len; i++) {
        	fis[i] = fileList.get(i);
        }
        return fis;
	}

	private Object instanceObject(Class c, HttpServletRequest request) {
		Object instance = null;
		try {
			instance = c.newInstance();
			Map m = request.getParameterMap();
			BeanUtils.copyProperties(instance, m);
		} catch (InstantiationException e) {
			e.printStackTrace();
			return null;
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			return null;
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			return null;
		}
		
		return instance;
	}

	private Object simpleRequestParam(String methodName, Class mc, HttpServletRequest request) {
		if (request.getParameter(methodName) == null)
			return null;
		return converter.convert(request.getParameter(methodName), mc);
	}

	private static boolean isSimpleValueType(Class clazz) {
		 return clazz.isPrimitive() || clazz.isEnum() ||
			CharSequence.class.isAssignableFrom(clazz) ||
			Number.class.isAssignableFrom(clazz) ||
			Date.class.isAssignableFrom(clazz);
	}
	
}
